using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;

namespace Needle.Utils.Parser
{
    /// <summary></summary>
    [DebuggerDisplay("CSharpTokenizer Index:{Index} Line:{Line} Column:{Column}")]
    public class CSharpTokenizer : StringTokenizer<CSharpToken>
    {

        #region private fields...

        private char[] _symbols = null;

        #endregion

        #region constructors...

        /// <summary></summary>
        public CSharpTokenizer(string s)
            : base(new StringReader(s))
        { }

        /// <summary></summary>
        public CSharpTokenizer(string s, char[] punctuators)
            : base(new StringReader(s), punctuators)
        { }

        /// <summary></summary>
        public CSharpTokenizer(TextReader reader)
            : base(reader)
        { }

        /// <summary></summary>
        public CSharpTokenizer(TextReader reader, char[] punctuators)
            : base(reader, punctuators)
        { }

        #endregion

        /// <summary></summary>
        protected override CSharpToken ReadToken()
        {
            char c;
            while (true)
            {
                c = Peek();
                switch (c)
                {
                    case '\'':
                        return ReadSQStringToken();
                    case '"':
                        return ReadDQStringToken();
                    case '@':
                        return ReadLiteralToken();
                    case '#':
                        return ReadPreProcessorToken();
                    default:
                        if (IsSymbol(c))
                            return ReadSymbolToken();
                        if (char.IsLetter(c) || c == '_' || c == '\\')
                            return ReadIdentifierToken(true);
                        if (char.IsNumber(c))
                            return ReadNumberToken();
                        throw new ParseException(Line, Column, "syntax error");
                }
            }
        }

        private CSharpToken ReadPreProcessorToken()
        {
            char c = Read();
            if (c != '#')
                throw new ParseException(Line, Column, "Expected '#' character");

            while (true)
            {
                c = Peek();
                switch (c)
                {
                    /* new-line characters */
                    case '\u000D':
                    case '\u000A':
                    case '\u2028':
                    case '\u2029':
                    case '\0':
                        return CreateToken(CSharpTokenType.PreProcessor);
                    default:
                        Read();
                        break;
                }
            }
        }

        private CSharpToken ReadSymbolToken()
        {
            if (Peek() == '/')
            {
                Read();
                switch (Peek())
                {
                    case '*':
                        return ReadInlineCommentToken();
                    case '/':
                        return ReadSingleLineCommentToken();
                    default:
                        break;
                }
            }
            while (IsSymbol(Peek()))
                Read();
            return CreateToken(CSharpTokenType.Symbol);
        }

        private CSharpToken ReadSingleLineCommentToken()
        {
            char c;
            while (true)
            {
                c = Peek();
                switch (c)
                {

                    case '\u000D':
                    case '\u000A':
                    case '\u2028':
                    case '\u2029':
                    case '\0':
                        return CreateToken(CSharpTokenType.Comment);
                    default:
                        Read();
                        break;
                }
            }
        }

        private CSharpToken ReadInlineCommentToken()
        {
            char c;
            int line = Line;
            int col = Column;
            int index = Index;
            while (true)
            {
                c = Peek();
                switch (c)
                {
                    case '\u000D':
                    case '\u000A':
                    case '\u2028':
                    case '\u2029':
                        ReadNewLine();
                        break;
                    case '*':
                        Read();
                        if (Peek() == '/')
                        {
                            Read();
                            return CreateToken(CSharpTokenType.Comment, line, col, index);
                        }
                        break;
                    case '\0':
                        throw new ParseException(Line, Column, "Unterminated inline comment");
                    default:
                        Read();
                        break;
                }
            }
        }
        KeywordCollection keywords = Keyword.Get();

        private CSharpToken ReadIdentifierToken(bool canBeKeyword)
        {
            bool escapedChar = false;
            while (true)
            {
                Char c = Peek();
                if (c == '\\')
                {
                    c = (char)ParseUnicodeEscapeSequence();
                    escapedChar = true;
                    canBeKeyword = false;
                }
                UnicodeCategory cat = char.GetUnicodeCategory(c);
                switch (cat)
                {
                    case UnicodeCategory.UppercaseLetter:
                    case UnicodeCategory.LowercaseLetter:
                    case UnicodeCategory.TitlecaseLetter:
                    case UnicodeCategory.ModifierLetter:
                    case UnicodeCategory.OtherLetter:
                    case UnicodeCategory.LetterNumber:
                    case UnicodeCategory.DecimalDigitNumber:
                    case UnicodeCategory.NonSpacingMark:
                    case UnicodeCategory.SpacingCombiningMark:
                    case UnicodeCategory.ConnectorPunctuation:
                    case UnicodeCategory.Format:
                        if (escapedChar)
                        {
                            Buffer.Append(c);
                            escapedChar = false;
                        }
                        else
                            Read();
                        break;
                    default:
                        CSharpTokenType type = CSharpTokenType.Identifier;
                        if (canBeKeyword && keywords.Contains(Buffer.ToString()))
                            type = CSharpTokenType.Keyword;
                        return CreateToken(type);
                }
            }
        }

        private CSharpToken ReadNumberToken()
        {
            Char c;
            while (char.IsDigit(c = Peek()) || c == '.' || c == ',')
                Read();
            return CreateToken(CSharpTokenType.Number);
        }

        private CSharpToken ReadLiteralToken()
        {
            char c = Read();
            if (c != '@')
                throw new ParseException(Line, Column, "Expected '@' character");
            if (Peek() == '"')
                Read();
            else if (char.IsLetter(Peek()))
            {
                DiscardLast();
                return ReadIdentifierToken(false);
            }

            while (true)
            {
                c = Peek();
                switch (c)
                {
                    case '"':
                        Read();
                        if (Peek() != '"')
                            return CreateToken(CSharpTokenType.String);
                        else
                            Discard();
                        break;
                    case '\0':
                        throw new ParseException(Line, Column, "Unterminated string literal.");
                    default:
                        Read();
                        break;
                }
            }
        }

        private CSharpToken ReadDQStringToken()
        {
            while (true)
            {
                char c = Peek();
                switch (c)
                {
                    /* new-line characters */
                    case '\u000D':
                    case '\u000A':
                    case '\u2028':
                    case '\u2029':
                    case '\0':
                        throw new ParseException(Line, Column, "Unterminated string literal");
                    case '\\':
                        Buffer.Append((char)ParseEscapeSequence());
                        continue;
                    case '"':
                        Read();
                        if (Buffer.Length == 1)
                            continue;
                        return CreateToken(CSharpTokenType.String);
                }
                Read();
            }
        }

        private CSharpToken ReadSQStringToken()
        {
            while (true)
            {
                char c = Peek();
                switch (c)
                {
                    /* new-line characters */
                    case '\u000D':
                    case '\u000A':
                    case '\u2028':
                    case '\u2029':
                    case '\0':
                        throw new ParseException(Line, Column, "Unterminated string literal");
                    case '\\':
                        Buffer.Append((char)ParseEscapeSequence());
                        continue;
                    case '\'':
                        Read();
                        if (Buffer.Length == 1)
                            continue;
                        return CreateToken(CSharpTokenType.String);
                }
                Read();
            }
        }

        private bool IsSymbol(char c)
        {
            char[] operators = _symbols;
            if (operators == null)
            {
                switch (c)
                {
                    case '+':
                    case '-':
                    case '&':
                    case '|':
                    case '<':
                    case '>':
                    case '=':
                    case '!':
                    case '*':
                    case '/':
                    case '%':
                    case '^':
                    case '?':
                        return true;
                    default:
                        return false;
                }
            }
            else
            {
                foreach (Char o in operators)
                    if (c == o)
                        return true;
            }
            return false;
        }

        /// <summary></summary>
        protected override CSharpToken CreateToken(TokenType type, int line, int col, int index)
        {
            String value = Buffer.ToString();
            Buffer.Length = 0;
            CSharpTokenType csType;
            switch (type)
            {
                case TokenType.Punctuator:
                    csType = CSharpTokenType.Symbol;
                    break;
                case TokenType.Token:
                    csType = CSharpTokenType.Unknown;
                    break;
                case TokenType.EOF:
                case TokenType.None:
                case TokenType.NewLine:
                case TokenType.WhiteSpace:
                default:
                    csType = CSharpTokenType.None;
                    break;
            }
            return new CSharpToken(value, type, csType, line, col, index);
        }

        /// <summary></summary>
        protected CSharpToken CreateToken(CSharpTokenType type)
        {
            String value = Buffer.ToString();
            Buffer.Length = 0;
            return new CSharpToken(
                       value,
                       TokenType.Token,
                       type,
                       Line,
                       Column - value.Length,
                       Index - value.Length);
        }

        /// <summary></summary>
        protected CSharpToken CreateToken(CSharpTokenType type, int line, int col, int index)
        {
            String value = Buffer.ToString();
            Buffer.Length = 0;
            return new CSharpToken(value, TokenType.Token, type, line, col, index);
        }

    }
}
